package com.mauth.web.service.impl;

import com.mauth.common.dto.auth.ClientParameter;
import com.mauth.common.dto.auth.LoginInfo;
import com.mauth.common.dto.auth.Token;
import com.mauth.common.dto.user.UserInfo;
import com.mauth.common.entity.auth.*;
import com.mauth.common.entity.user.User;
import com.mauth.web.repository.ClientRepository;
import com.mauth.web.service.AuthService;
import com.mauth.web.service.AuthorizedCodeService;
import com.mauth.web.service.UserService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpSession;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class AuthServiceImpl implements AuthService {
    private static final Base64.Decoder decoder = Base64.getDecoder();
    @Value("${system.expires}")
    private Integer expires;
    @Resource
    private ClientRepository clientRepository;
    @Resource
    private UserService userService;

    @Resource
    private AuthorizedCodeService codeService;
    public static final int HALF_MONTH = 1000 * 3600 * 24 * 15;

    @Override
    public boolean isLogin(ClientParameter parameter, HttpSession session) {
        session.setAttribute("parameter", parameter);
        return session.getAttribute("loginInfo") != null;
    }

    @Override
    public ClientParameter getOauth2Parameter(HttpSession session) {
        if (session.getAttribute("parameter") instanceof ClientParameter) {
            return (ClientParameter) session.getAttribute("parameter");
        }
        return null;
    }

    @Override
    @SneakyThrows
    public boolean validAuth(String code, String authorization) {
        authorization = authorization.replace("Basic ", "");
        String authString = new String(Base64.getDecoder().decode(authorization), StandardCharsets.UTF_8);
        return codeService.validCode(code) && validClientInfo(authString);
    }

    public String getClientSecret(String authorization) {
        authorization = authorization.replace("Basic ", "");
        String authString = new String(decoder.decode(authorization), StandardCharsets.UTF_8);
        if (authString.contains(":")) {
            return authString.split(":")[1];
        }
        return "";
    }

    @Override
    public Token generateToken(ClientParameter parameter, String authorization) {
        String code = parameter.getCode();
        UserInfo userInfo = getUserInfo(code);
        if (code != null && userInfo != null) {
            Map<String, Object> tokenInfo = new HashMap<>();
            tokenInfo.put("username", userInfo.getUserName());
            tokenInfo.put("domain", parameter.getClient_id());
            tokenInfo.put("permissions", getUserAuthString(userInfo.getId()));
            tokenInfo.put("loginTime", new Date());
            tokenInfo.put("expires", expires);
            SecretKey key = Keys.hmacShaKeyFor(getClientSecret(authorization).getBytes());
            String accessToken = Jwts.builder().setClaims(tokenInfo).signWith(key).compact();
            String refreshToken = Jwts.builder().setClaims(tokenInfo).signWith(key).compact();
            return new Token(accessToken, HALF_MONTH, refreshToken, parameter.getScopes(), "authorization_code");
        }
        return null;
    }

    private List<String> getUserAuthString(Integer userId) {
        UserRole userRole = userService.getUserAuth(userId);
        List<String> userAuthorities = new ArrayList<>();
        Set<Role> roles = userRole.getRoles();
        for (Role role : roles) {
            String authority = "";
            String roleName = role.getName();
            String scopes = role.getScopes().stream().map(Scope::getName)
                    .collect(Collectors.joining(",", "[", "]"));
            Set<UserAuthority> authorities = role.getUserAuthorities();
            for (UserAuthority userAuthority : authorities) {
                String authorityName = userAuthority.getAuthority().getName();
                String qualifiers = userAuthority.getQualifiers().stream().map(Qualifier::getName)
                        .collect(Collectors.joining(",", "[", "]"));
                authority = roleName + scopes + ":" + authorityName + qualifiers;
                userAuthorities.add(authority);
            }
        }
        return userAuthorities;
    }

    private UserInfo getUserInfo(String code) {
        AuthorizedCode authorizedCode = codeService.getAuthorizedCode(code);
        if (authorizedCode != null) {
            User user = userService.getUser(authorizedCode.getUserSeq());
            codeService.deleteAuthorizedCode(authorizedCode.getId());
            return UserInfo.builder()
                    .id(user.getId())
                    .userName(user.getUserName())
                    .build();
        }
        return null;
    }

    @Override
    public String generateCode(HttpSession session) {
        Object loginInfo = session.getAttribute("loginInfo");
        if (loginInfo instanceof LoginInfo) {
            LoginInfo info = (LoginInfo) loginInfo;
            AuthorizedCode code = codeService.generateCode(info.getUserSeq(), info.getClientId(), expires);
            return code.getCode();
        }
        return null;
    }

    private boolean validClientInfo(String authString) {
        if (authString.contains(":")) {
            String[] split = authString.split(":");
            String clientId = split[0];
            Client byClientId = clientRepository.findByClientId(clientId);
            if (byClientId != null) {
                return byClientId.getClientSecret().equals(split[1]);
            }
        }

        return false;
    }
}
